class Backtest1:

    def __init__(self, Bars, firstcash, no_risk_rate, tax_rate1=0.001, tax_rate2=0.0001, tax_rate3=0.0003):
        self.stock = Bars  # 股票对象的列表
        # 股票代码的列表
        self.stocklist = []
        stockinformation = []
        for bar in self.stock:
            self.stocklist.append(bar.code)
            stockinformation.append([0,0,0])
        # 一个DataFrame，index是股票代码，volumns是股票持有数量（股）、实际权重、市值
        self.hold_stock = pd.DataFrame(stockinformation , index=self.stocklist, columns=["股票数量","权重","市值"])
        self.available_stocklist = [] # 可交易股票的列表
        self.firstcash = firstcash #个人拥有的可用于投资的资金
        self.cash = firstcash #个人拥有的可用于投资的资金
        self.total_value = 0  # 拥有的投资组合总价值
        self.available_value = 0 # 去掉nan的股票的市值，可用的总资金
        self.unavailable_value = 0 # nan的股票的总市值
        self.tax_rate1 = tax_rate1 # 印花税千分之一，卖方支付；
        self.tax_rate2 = tax_rate2 # 过户费万分之0.2，双方支付；其他费用可以加在过户费上面;总的估计为万分之一
        self.tax_rate3 = tax_rate3 # 佣金暂定万分之三，双方支付（最小为5）；
        self.totalyield = 1
        self.no_risk_rate = no_risk_rate # 无风险利率字典

    # 买入指令(均价，购买所用资金，成交量)
    def buy(self, price, cash_available , volume):
        if cash_available <= 0 or math.isnan(volume) or volume == 0:
            return [0,0]
        else :
            price0 = price * 100 #每手股票总价
            price1 = price0 * (1 + self.tax_rate2 + self.tax_rate3) # 每手含交易成本的总价
            delta_position0 = cash_available // price1  # 初步判断可买多少手股票
            delta_value0 = delta_position0 * price0  # 初步判断可买股票的价值
            tax2 = delta_value0 * self.tax_rate2
            tax3 = delta_value0 * self.tax_rate3

            # 考虑是否达到最小佣金的情况
            if delta_position0 > 0 and tax3 < 5:  # 佣金费没有达到最小值
                tax3 = 5
                cash_available0 = (cash_available - tax3) / (1 + self.tax_rate2)  # 可用资金
                delta_position = cash_available0 // price0  # 买入多少手股票
                delta_value = delta_position * price0  # 买入股票的价值
                tax2 = delta_value * self.tax_rate2
            else:  # 佣金达到最小值
                delta_position = delta_position0  # 买入多少手股票
                delta_value = delta_value0  # 买入股票的价值

            if delta_position * 100 > volume: #如果购买股票数量大于股票当天成交数量
                print("购买",delta_position * 100,"数量超过当天成交",volume,"数量")
                delta_position = volume // 100
                delta_value = delta_position * price0  # 买入股票的价值
                tax2 = delta_value * self.tax_rate2
                tax3 = delta_value * self.tax_rate3

            total_value = delta_value + tax2 + tax3
            list = [delta_position * 100 , total_value]
            #[买入股票数量，含交易成本所花总价]
            return list

    # 卖出指令（当前持有股票数，均价，应该减少的该股票市值（正数），成交量）
    def sell(self, position, price, sell_value, volume):
        if position <= 0 or math.isnan(volume) or volume == 0:
            return [0,0]
        else:
            delta_position = sell_value // price #应该卖出的股票数量

            if delta_position > position :
                delta_position = position
            elif delta_position > volume:
                print("卖出",delta_position * 100,"数量超过当天成交",volume,"数量")
                delta_position = volume

            delta_value = price * delta_position  # 交易股票价值
            tax1 = delta_value * self.tax_rate1
            tax2 = delta_value * self.tax_rate2
            tax3 = delta_value * self.tax_rate3

            if tax3 < 5:  # 佣金费没有达到最小值
                tax3 = 5

            total_value = delta_value - tax1 - tax2 - tax3
            list = [delta_position , total_value]
            #[卖出股票数量，去掉交易成本得到的现金]
            return list

    def get_yield(self, df , day_count = 1):
        """
        回测思路：如果是第一个交易日，对于非停牌股票按权重进行购买，停牌股票不购买
                从第二个交易日开始，每天首先根据股票开盘价更新总市值、各股票市值和实际权重、要变化的权重，
                    将要调仓的股票按变化权重从小（可能为负）到大排序，按顺序（如果有卖出就会先卖出）进行调仓，
                    记录当天调仓操作，持股情况（包括现金），调仓完成后的总市值，收益率和累计收益率，
                    并将当天记录日志添加到总日志
                最后，将总的日志输出为csv文件，第一列是日期，第二列是每天的调仓操作，第三列是每天的持股情况，
                    第四列是每天的收益率，第五列是累计的收益率
        :param df: dataFrame, 策略得出的回测期间内的每日目标权重
        """
        datelist = df.index.tolist() # 一个日期列表
        stocklist = df.columns.tolist()
        record = []
        for date in datelist:
            day_caozuo = ""
            day_holdstock = ""
            self.available_value = 0
            day_record = []
            if date == datelist[0]:
                #第一天建仓
                last_value = self.firstcash
                changerate = df.loc[date] # 得到一个series，index是股票代码，values是权重
                for stock0 in self.stock:
                    if math.isnan(stock0.stock_data.loc[date,"avg"]):
                        pass
                    else:
                        info = self.buy(stock0.stock_data.loc[date,"avg"] , df.loc[date, stock0.code] * self.firstcash , stock0.stock_data.loc[date,"volume"])
                        self.hold_stock.loc[stock0.code,"股票数量"] = info[0]  # 返回股票现有仓位
                        self.hold_stock.loc[stock0.code,"市值"] = info[0] * stock0.stock_data.loc[date,"avg"] # 返回股票现有价值
                        self.available_value += self.hold_stock.loc[stock0.code,"市值"]
                        self.hold_stock.loc[stock0,"权重"] = self.hold_stock.loc[stock0.code,"市值"] / self.firstcash
                        self.cash -= info[1] #减少可用现金
                        if info[0] > 0:
                            day_caozuo = day_caozuo + str(stock0.code + "买入" + str(info[0]) + "股;")
            else:
                # 非第一天调仓
                # 先更新按新一天的开盘价得到的总市值、各股票市值和实际权重、要变化的权重
                self.unavailable_value = 0
                self.available_value = 0
                self.available_stocklist = []
                for stock0 in self.stock:
                    openprice = stock0.stock_data.loc[date,"open"]
                    if math.isnan(openprice) : # 如果是nan,市值不更新
                        if self.hold_stock.loc[stock0.code,"市值"] != 0:
                            self.unavailable_value += self.hold_stock.loc[stock0.code,"市值"]
                        continue
                    else:
                        self.available_stocklist.append(stock0.code)
                        self.hold_stock.loc[stock0.code,"市值"] = self.hold_stock.loc[stock0.code,"股票数量"] * openprice
                        self.available_value += self.hold_stock.loc[stock0.code,"市值"]
                self.cash = self.cash * (1 + self.no_risk_rate[date]) # 闲置资金获得收益
                self.available_value += self.cash
                self.total_value = self.unavailable_value + self.available_value # 总市值
                self.hold_stock["权重"] = self.hold_stock["市值"].div(self.total_value)

                changerate = df.loc[date] - self.hold_stock["权重"] # 获取权重的变化
                changerate.sort_values(ascending=True,inplace=True) # 将权重排序

                # 对非nan的股票进行调仓，将调仓操作记录下来
                self.available_value = 0
                for stock0 in self.available_stocklist:
                    for bar in self.stock:
                        if bar.code == stock0: # 定位到stock
                            price = bar.stock_data.loc[date,"avg"]
                            volume = bar.stock_data.loc[date,"volume"]
                            if changerate[stock0] == 0:
                                self.hold_stock.loc[bar.code,"市值"] = self.hold_stock.loc[bar.code,"股票数量"] * bar.stock_data.loc[date,"avg"]
                                self.available_value += self.hold_stock.loc[bar.code,"市值"]
                                pass
                            elif changerate[stock0] < 0:    # 权重小于0，卖
                                #（当前持有股票数，均价，应该减少的该股票市值（正数），成交量）
                                info = self.sell(self.hold_stock.loc[bar.code,"股票数量"], bar.stock_data.loc[date,"avg"], -changerate[stock0] * self.total_value, bar.stock_data.loc[date,"volume"])
                                self.hold_stock.loc[bar.code,"股票数量"] -= info[0]
                                self.hold_stock.loc[bar.code,"市值"] = self.hold_stock.loc[bar.code,"股票数量"] * bar.stock_data.loc[date,"avg"]
                                self.cash += info[1]
                                self.available_value += self.hold_stock.loc[bar.code,"市值"]
                                if info[0] > 0:
                                    day_caozuo = day_caozuo + str(stock0 + "卖出" + str(info[0]) + "股;")
                            else:     # 权重大于0，买
                                # （均价，购买所用资金，成交量）
                                info = self.buy(bar.stock_data.loc[date,"avg"], min(changerate[stock0] * self.total_value , self.cash), bar.stock_data.loc[date,"volume"])
                                self.hold_stock.loc[bar.code,"股票数量"] += info[0]
                                self.hold_stock.loc[bar.code,"市值"] = self.hold_stock.loc[bar.code,"股票数量"] * bar.stock_data.loc[date,"avg"]
                                self.cash -= info[1]
                                self.available_value += self.hold_stock.loc[bar.code,"市值"]
                                if info[0] > 0:
                                    day_caozuo = day_caozuo + str(stock0 + "买入" + str(info[0]) + "股;")

            # 记录当天的持股情况（包括现金）,调仓完成后的总市值
            for stock0 in self.stock:
                day_holdstock = day_holdstock + str(stock0.code + ":" + str(self.hold_stock.loc[stock0.code,"股票数量"]) + "股;")
            self.hold_stock.loc["freecash","股票数量"] = round(self.cash, 2)
            day_holdstock = day_holdstock + str("现金" + ":" + str(self.hold_stock.loc["freecash","股票数量"]) + "份;")
            self.available_value += self.cash
            self.total_value = self.unavailable_value + self.available_value # 总市值

            # 计算当天收益率和累计收益率，更新前一天总市值
            returnrate = self.total_value / last_value - 1
            self.totalyield = self.totalyield * ( 1 + returnrate )
            last_value = self.total_value

            # 更新按当天均价得到的各股票市值占总市值权重
            for stock0 in self.stock:
                self.hold_stock.loc[stock0,"权重"] = self.hold_stock.loc[stock0.code,"市值"] / self.total_value
            self.hold_stock.loc["freecash","权重"] = self.cash / self.total_value

            # 将当天记录日志添加到总日志
            if day_caozuo != "":
                day_caozuo = day_caozuo[0:-1]
            else :
                day_caozuo = "无调仓"
            day_record= [day_caozuo]
            day_holdstock = day_holdstock[0:-1]
            day_record.append(day_holdstock)
            day_record.append(returnrate)
            day_record.append(self.totalyield)
            #sharp = (list[2].mean()*365-0.035)/(list[2].std()*365)
            #day_record.append(sharp)
            record.append(day_record)

        result = pd.DataFrame(record, index=datelist, columns=["操作","投资组合情况","收益率","累计收益率"])
        #,"夏普比率","最大回撤"
        result.to_csv("./result.csv")